#include "Thread.hpp"
#include <queue>
#include <functional>
#include <unistd.h>
#include <cstdbool>
#include "Log.hpp"
using namespace ThreadMoudle;

namespace bit
{
    const int default_size = 5;

    template <class T>
    class ThreadPool
    {
    private:
        pthread_mutex_t _poolmutex = PTHREAD_MUTEX_INITIALIZER;
        // 加锁操作：
        int _Lock(pthread_mutex_t &mutex)
        {
            return pthread_mutex_lock(&mutex);
        }
        // 解锁操作：
        int _Unlock(pthread_mutex_t &mutex)
        {
            return pthread_mutex_unlock(&mutex);
        }
        // 线程的等待：
        int _Wait()
        {
            return pthread_cond_wait(&_cond, &_mutex);
        }
        // 唤醒：
        // 唤醒单个线程：
        int _Wakeup()
        {
            return pthread_cond_signal(&_cond);
        }
        // 唤醒所有线程：
        int _WakeupAll()
        {
            return pthread_cond_broadcast(&_cond);
        }
        // 每个线程都在跑这个任务：
        void _HanderTask(const std::string &name)
        {
            // 一进来就进行自我分离，然后就不需要join啦：
            pthread_detach(pthread_self());
            while (true)
            {
                _Lock(_mutex);
                // 用while循环来判断，防止伪唤醒的情况发生：
                // 情况一：线程池活跃，但任务队列为空 —— 线程都去等待；
                while (_task_queue.empty() && _isrunning)
                {
                    _sleepThread_num++;
                    _Wait();
                    _sleepThread_num--;
                }
                //情况二：线程池不活跃了，且任务队列为空 —— 线程就退出；
                //易错点：不要抱着锁退出哦：
                if(_task_queue.empty() && !_isrunning)
                {
                    //std::cout << name << "died." << std::endl;
                    LOG(INFO, "%s died.", name.c_str());
                    _Unlock(_mutex);
                    break; 
                }
                // 来到这里，队列一定不为空：
                T t = _task_queue.front();
                _task_queue.pop();
                _Unlock(_mutex);

                // 处理任务：
                std::cout << name << "->";
                t();
            }
        }

    public:
        // 构造函数：
        ThreadPool(size_t thread_num = default_size) : _thread_num(thread_num), _isrunning(false)
        {
            typename Thread::func_t func = std::bind(&ThreadPool::_HanderTask, this, std::placeholders::_1);
            for (size_t i = 0; i < thread_num; i++)
            {
                const std::string name = "thread-" + std::to_string(i + 1);
                _threads_pool.emplace_back(name, func);
            }
            pthread_mutex_init(&_mutex, nullptr);
            pthread_cond_init(&_cond, nullptr);
        }
        // 线程池的启动：
        void Start()
        {
            // 为了防止多个线程并发访问线程池，导致线程池的启动状态不确定问题，需要加锁；
            // 而且不应该和线程池中的其他线程，形成互斥关系，也就是不能和它们用同一把锁，不然太影响效率了：
            _Lock(_poolmutex);
            _isrunning = true;
            for (auto &k : _threads_pool)
            {
                k.Start();
            }
            _Unlock(_poolmutex);
        }
        // 线程的停止：
        void Stop()
        {
            _Lock(_poolmutex);
            _isrunning = false;
            // 有些线程可能此时还在条件变量下等待，不唤醒它们可能就永远无法退出：
            _WakeupAll();
            _Unlock(_poolmutex);
        }
        // 任务入队列的操作：
        void Enqueue(const T &in)
        {
            _Lock(_mutex);
            //只有当线程池活跃时，我们才能push任务进来：
            if (_isrunning)
            {
                _task_queue.push(in);
                if (_sleepThread_num > 0)
                {
                    _Wakeup();
                }
            }
            _Unlock(_mutex);
        }
        // 析构函数：
        ~ThreadPool()
        {
            pthread_mutex_destroy(&_mutex);
            pthread_cond_destroy(&_cond);
        }

    private:
        size_t _thread_num;                // 线程池中总的线程数量；
        size_t _sleepThread_num = 0;       // 线程池中休眠线程的数量；
        std::vector<Thread> _threads_pool; // 线程池

        // 生产线程要push任务，多消费线程之间要pop任务，所以任务队列一定要是一个临界资源：
        std::queue<T> _task_queue; // 任务队列

        bool _isrunning; // 线程池的状态

        // 由于queue 在pop和push时，我们并不能保证说，不会对同一个字段进行访问，所以没法保证让生产和消费同时进行，所以需要只能用互斥锁：
        pthread_mutex_t _mutex; // 互斥锁
        pthread_cond_t _cond;   // 条件变量
    };
}